﻿#region Header

// <file>
//     <copyright see="prj:///doc/copyright.txt"/>
//     <license see="prj:///doc/license.txt"/>
//     <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
//     <version>$Revision: 3078 $</version>
// </file>

#endregion Header

namespace ICSharpCode.TextEditor
{
    using System;
    using System.ComponentModel;
    using System.Drawing;
    using System.Drawing.Printing;
    using System.Windows.Forms;

    using ICSharpCode.TextEditor.Document;

    /// <summary>
    /// This class is used for a basic text area control
    /// </summary>
    [ToolboxBitmap("ICSharpCode.TextEditor.Resources.TextEditorControl.bmp")]
    [ToolboxItem(true)]
    public class TextEditorControl : TextEditorControlBase
    {
        #region Fields

        protected Panel textAreaPanel = new Panel();

        TextAreaControl activeTextAreaControl;
        int curLineNr = 0;
        float curTabIndent = 0;
        TextAreaControl primaryTextArea;
        PrintDocument printDocument = null;
        StringFormat printingStringFormat;
        TextAreaControl secondaryTextArea = null;
        Splitter textAreaSplitter = null;

        #endregion Fields

        #region Constructors

        public TextEditorControl()
        {
            SetStyle(ControlStyles.ContainerControl, true);

            textAreaPanel.Dock = DockStyle.Fill;

            Document = (new DocumentFactory()).CreateDocument();
            Document.HighlightingStrategy = HighlightingStrategyFactory.CreateHighlightingStrategy();

            primaryTextArea  = new TextAreaControl(this);
            activeTextAreaControl = primaryTextArea;
            primaryTextArea.TextArea.GotFocus += delegate {
                SetActiveTextAreaControl(primaryTextArea);
            };
            primaryTextArea.Dock = DockStyle.Fill;
            textAreaPanel.Controls.Add(primaryTextArea);
            InitializeTextAreaControl(primaryTextArea);
            Controls.Add(textAreaPanel);
            ResizeRedraw = true;
            Document.UpdateCommited += new EventHandler(CommitUpdateRequested);
            OptionsChanged();
        }

        #endregion Constructors

        #region Events

        public event EventHandler ActiveTextAreaControlChanged;

        #endregion Events

        #region Properties

        public override TextAreaControl ActiveTextAreaControl
        {
            get {
                return activeTextAreaControl;
            }
        }

        [Browsable(false)]
        public bool EnableRedo
        {
            get {
                return Document.UndoStack.CanRedo;
            }
        }

        [Browsable(false)]
        public bool EnableUndo
        {
            get {
                return Document.UndoStack.CanUndo;
            }
        }

        [Browsable(false)]
        public PrintDocument PrintDocument
        {
            get {
                if (printDocument == null) {
                    printDocument = new PrintDocument();
                    printDocument.BeginPrint += new PrintEventHandler(this.BeginPrint);
                    printDocument.PrintPage  += new PrintPageEventHandler(this.PrintPage);
                }
                return printDocument;
            }
        }

        #endregion Properties

        #region Methods

        public override void EndUpdate()
        {
            base.EndUpdate();
            Document.CommitUpdate();
            if (!IsInUpdate) {
                ActiveTextAreaControl.Caret.OnEndUpdate();
            }
        }

        public override void OptionsChanged()
        {
            primaryTextArea.OptionsChanged();
            if (secondaryTextArea != null) {
                secondaryTextArea.OptionsChanged();
            }
        }

        public void Redo()
        {
            if (Document.ReadOnly) {
                return;
            }
            if (Document.UndoStack.CanRedo) {
                BeginUpdate();
                Document.UndoStack.Redo();

                Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
                this.primaryTextArea.TextArea.UpdateMatchingBracket();
                if (secondaryTextArea != null) {
                    this.secondaryTextArea.TextArea.UpdateMatchingBracket();
                }
                EndUpdate();
            }
        }

        public void SetHighlighting(string name)
        {
            Document.HighlightingStrategy = HighlightingStrategyFactory.CreateHighlightingStrategy(name);
        }

        public void Split()
        {
            if (secondaryTextArea == null) {
                secondaryTextArea = new TextAreaControl(this);
                secondaryTextArea.Dock = DockStyle.Bottom;
                secondaryTextArea.Height = Height / 2;

                secondaryTextArea.TextArea.GotFocus += delegate {
                    SetActiveTextAreaControl(secondaryTextArea);
                };

                textAreaSplitter =  new Splitter();
                textAreaSplitter.BorderStyle = BorderStyle.FixedSingle ;
                textAreaSplitter.Height = 8;
                textAreaSplitter.Dock = DockStyle.Bottom;
                textAreaPanel.Controls.Add(textAreaSplitter);
                textAreaPanel.Controls.Add(secondaryTextArea);
                InitializeTextAreaControl(secondaryTextArea);
                secondaryTextArea.OptionsChanged();
            } else {
                SetActiveTextAreaControl(primaryTextArea);

                textAreaPanel.Controls.Remove(secondaryTextArea);
                textAreaPanel.Controls.Remove(textAreaSplitter);

                secondaryTextArea.Dispose();
                textAreaSplitter.Dispose();
                secondaryTextArea = null;
                textAreaSplitter  = null;
            }
        }

        public void Undo()
        {
            if (Document.ReadOnly) {
                return;
            }
            if (Document.UndoStack.CanUndo) {
                BeginUpdate();
                Document.UndoStack.Undo();

                Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
                this.primaryTextArea.TextArea.UpdateMatchingBracket();
                if (secondaryTextArea != null) {
                    this.secondaryTextArea.TextArea.UpdateMatchingBracket();
                }
                EndUpdate();
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing) {
                if (printDocument != null) {
                    printDocument.BeginPrint -= new PrintEventHandler(this.BeginPrint);
                    printDocument.PrintPage  -= new PrintPageEventHandler(this.PrintPage);
                    printDocument = null;
                }
                Document.UndoStack.ClearAll();
                Document.UpdateCommited -= new EventHandler(CommitUpdateRequested);
                if (textAreaPanel != null) {
                    if (secondaryTextArea != null) {
                        secondaryTextArea.Dispose();
                        textAreaSplitter.Dispose();
                        secondaryTextArea = null;
                        textAreaSplitter  = null;
                    }
                    if (primaryTextArea != null) {
                        primaryTextArea.Dispose();
                    }
                    textAreaPanel.Dispose();
                    textAreaPanel = null;
                }
            }
            base.Dispose(disposing);
        }

        protected virtual void InitializeTextAreaControl(TextAreaControl newControl)
        {
        }

        protected void SetActiveTextAreaControl(TextAreaControl value)
        {
            if (activeTextAreaControl != value) {
                activeTextAreaControl = value;

                if (ActiveTextAreaControlChanged != null) {
                    ActiveTextAreaControlChanged(this, EventArgs.Empty);
                }
            }
        }

        void Advance(ref float x, ref float y, float maxWidth, float size, float fontHeight)
        {
            if (x + size < maxWidth) {
                x += size;
            } else {
                x  = curTabIndent;
                y += fontHeight;
            }
        }

        void BeginPrint(object sender, PrintEventArgs ev)
        {
            curLineNr = 0;
            printingStringFormat = (StringFormat)System.Drawing.StringFormat.GenericTypographic.Clone();

            // 100 should be enough for everyone ...err ?
            float[] tabStops = new float[100];
            for (int i = 0; i < tabStops.Length; ++i) {
                tabStops[i] = TabIndent * primaryTextArea.TextArea.TextView.WideSpaceWidth;
            }

            printingStringFormat.SetTabStops(0, tabStops);
        }

        void CommitUpdateRequested(object sender, EventArgs e)
        {
            if (IsInUpdate) {
                return;
            }
            foreach (TextAreaUpdate update in Document.UpdateQueue) {
                switch (update.TextAreaUpdateType) {
                    case TextAreaUpdateType.PositionToEnd:
                        this.primaryTextArea.TextArea.UpdateToEnd(update.Position.Y);
                        if (this.secondaryTextArea != null) {
                            this.secondaryTextArea.TextArea.UpdateToEnd(update.Position.Y);
                        }
                        break;
                    case TextAreaUpdateType.PositionToLineEnd:
                    case TextAreaUpdateType.SingleLine:
                        this.primaryTextArea.TextArea.UpdateLine(update.Position.Y);
                        if (this.secondaryTextArea != null) {
                            this.secondaryTextArea.TextArea.UpdateLine(update.Position.Y);
                        }
                        break;
                    case TextAreaUpdateType.SinglePosition:
                        this.primaryTextArea.TextArea.UpdateLine(update.Position.Y, update.Position.X, update.Position.X);
                        if (this.secondaryTextArea != null) {
                            this.secondaryTextArea.TextArea.UpdateLine(update.Position.Y, update.Position.X, update.Position.X);
                        }
                        break;
                    case TextAreaUpdateType.LinesBetween:
                        this.primaryTextArea.TextArea.UpdateLines(update.Position.X, update.Position.Y);
                        if (this.secondaryTextArea != null) {
                            this.secondaryTextArea.TextArea.UpdateLines(update.Position.X, update.Position.Y);
                        }
                        break;
                    case TextAreaUpdateType.WholeTextArea:
                        this.primaryTextArea.TextArea.Invalidate();
                        if (this.secondaryTextArea != null) {
                            this.secondaryTextArea.TextArea.Invalidate();
                        }
                        break;
                }
            }
            Document.UpdateQueue.Clear();
            //			this.primaryTextArea.TextArea.Update();
            //			if (this.secondaryTextArea != null) {
            //				this.secondaryTextArea.TextArea.Update();
            //			}
        }

        void DrawLine(Graphics g, LineSegment line, float yPos, RectangleF margin)
        {
            float xPos = 0;
            float fontHeight = Font.GetHeight(g);
            //			bool  gotNonWhitespace = false;
            curTabIndent = 0 ;

            FontContainer fontContainer = TextEditorProperties.FontContainer;
            foreach (TextWord word in line.Words) {
                switch (word.Type) {
                    case TextWordType.Space:
                        Advance(ref xPos, ref yPos, margin.Width, primaryTextArea.TextArea.TextView.SpaceWidth, fontHeight);
            //						if (!gotNonWhitespace) {
            //							curTabIndent = xPos;
            //						}
                        break;
                    case TextWordType.Tab:
                        Advance(ref xPos, ref yPos, margin.Width, TabIndent * primaryTextArea.TextArea.TextView.WideSpaceWidth, fontHeight);
            //						if (!gotNonWhitespace) {
            //							curTabIndent = xPos;
            //						}
                        break;
                    case TextWordType.Word:
            //						if (!gotNonWhitespace) {
            //							gotNonWhitespace = true;
            //							curTabIndent    += TabIndent * primaryTextArea.TextArea.TextView.GetWidth(' ');
            //						}
                        g.DrawString(word.Word, word.GetFont(fontContainer), BrushRegistry.GetBrush(word.Color), xPos + margin.X, yPos);
                        SizeF drawingSize = g.MeasureString(word.Word, word.GetFont(fontContainer), new SizeF(margin.Width, fontHeight * 100), printingStringFormat);
                        Advance(ref xPos, ref yPos, margin.Width, drawingSize.Width, fontHeight);
                        break;
                }
            }
        }

        // btw. I hate source code duplication ... but this time I don't care !!!!
        float MeasurePrintingHeight(Graphics g, LineSegment line, float maxWidth)
        {
            float xPos = 0;
            float yPos = 0;
            float fontHeight = Font.GetHeight(g);
            //			bool  gotNonWhitespace = false;
            curTabIndent = 0;
            FontContainer fontContainer = TextEditorProperties.FontContainer;
            foreach (TextWord word in line.Words) {
                switch (word.Type) {
                    case TextWordType.Space:
                        Advance(ref xPos, ref yPos, maxWidth, primaryTextArea.TextArea.TextView.SpaceWidth, fontHeight);
            //						if (!gotNonWhitespace) {
            //							curTabIndent = xPos;
            //						}
                        break;
                    case TextWordType.Tab:
                        Advance(ref xPos, ref yPos, maxWidth, TabIndent * primaryTextArea.TextArea.TextView.WideSpaceWidth, fontHeight);
            //						if (!gotNonWhitespace) {
            //							curTabIndent = xPos;
            //						}
                        break;
                    case TextWordType.Word:
            //						if (!gotNonWhitespace) {
            //							gotNonWhitespace = true;
            //							curTabIndent    += TabIndent * primaryTextArea.TextArea.TextView.GetWidth(' ');
            //						}
                        SizeF drawingSize = g.MeasureString(word.Word, word.GetFont(fontContainer), new SizeF(maxWidth, fontHeight * 100), printingStringFormat);
                        Advance(ref xPos, ref yPos, maxWidth, drawingSize.Width, fontHeight);
                        break;
                }
            }
            return yPos + fontHeight;
        }

        void PrintPage(object sender, PrintPageEventArgs ev)
        {
            Graphics g = ev.Graphics;
            float yPos = ev.MarginBounds.Top;

            while (curLineNr < Document.TotalNumberOfLines) {
                LineSegment curLine  = Document.GetLineSegment(curLineNr);
                if (curLine.Words != null) {
                    float drawingHeight = MeasurePrintingHeight(g, curLine, ev.MarginBounds.Width);
                    if (drawingHeight + yPos > ev.MarginBounds.Bottom) {
                        break;
                    }

                    DrawLine(g, curLine, yPos, ev.MarginBounds);
                    yPos += drawingHeight;
                }
                ++curLineNr;
            }

            // If more lines exist, print another page.
            ev.HasMorePages = curLineNr < Document.TotalNumberOfLines;
        }

        #endregion Methods
    }
}